package dryven.unittest;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import dryven.NotImplementedException;
import dryven.annotations.Default;
import dryven.model.di.DefaultDependencyService;
import dryven.model.di.DependencyInjector;
import dryven.model.di.DependencyInjectorBinder;
import dryven.model.di.DependencyService;
import dryven.model.di.LocalThreadStorage;
import dryven.request.controller.ControllerDescription;
import dryven.request.controller.DependencyInjectingControllerFactory;
import dryven.request.controller.paramtransform.ActionParameterBindCandidate;
import dryven.request.controller.paramtransform.ActionParameterFillCandidate;
import dryven.request.controller.paramtransform.ActionParameterTransformCandidate;
import dryven.request.controller.paramtransform.ActionParameterTransformResult;
import dryven.request.controller.paramtransform.ActionParameterTransformer;
import dryven.request.controller.paramtransform.CompositeActionParameterTransformer;
import dryven.request.controller.paramtransform.baseimpl.DefaultValueFiller;
import dryven.request.controller.paramtransform.baseimpl.RequestBinder;
import dryven.request.controller.paramtransform.baseimpl.PrimitiveTypeBinder;
import dryven.request.controller.paramtransform.baseimpl.UrlDecodeTransformer;
import dryven.request.controller.result.ActionResult;
import dryven.request.controller.result.ForwardResult;
import dryven.request.http.Request;
import dryven.request.routing.routers.PackageRouter;
import dryven.unittest.mocks.MockRequest;
import dryven.unittest.mocks.MockResponse;
import dryven.unittest.testcontrollers.dispatch.TestController;
import dryven.unittest.testcontrollers.dispatch.TestDependency;
import dryven.unittest.testcontrollers.dispatch.TestController.Flavour;
import dryven.unittest.testcontrollers.list.ListController;
import dryven.unittest.testcontrollers.list.IndexController.Animal;
import dryven.util.ArrayIterable;
import junit.framework.TestCase;

public class ControllerDispatchTest extends TestCase {
	
	public enum Mammal {
		Whale, Tiger, Hedgehog, Human, Rabbit, Rat
	};
	
	public enum DependencyInjectedType {
		DIEqualsCool, DIEqualsNerdy, DIEqualsNotCoolAtAll, DIEqualsRules
	};
	
	
	private ActionParameterTransformer createTransformer() {
		Iterable<ActionParameterTransformCandidate> transformers = new ArrayIterable<ActionParameterTransformCandidate>(new ActionParameterTransformCandidate[] {new UrlDecodeTransformer()});
		Iterable<ActionParameterBindCandidate> binders = new ArrayIterable<ActionParameterBindCandidate>(new ActionParameterBindCandidate[] {new PrimitiveTypeBinder(), new RequestBinder()});
		Iterable<ActionParameterFillCandidate> fillers = new ArrayIterable<ActionParameterFillCandidate>(new ActionParameterFillCandidate[] {new DefaultValueFiller()});
		
		ActionParameterTransformer tr = new CompositeActionParameterTransformer(transformers,binders,fillers);
		return tr;
	}
	
	private ActionParameterTransformer createDITestTransformer() {
		Iterable<ActionParameterTransformCandidate> transformers = new ArrayIterable<ActionParameterTransformCandidate>(new ActionParameterTransformCandidate[] {});
		Iterable<ActionParameterBindCandidate> binders = new ArrayIterable<ActionParameterBindCandidate>(new ActionParameterBindCandidate[] {new DependencyInjectorBinder(new DependencyInjector() {
			
			@Override
			public Object load(LocalThreadStorage storage, Class<?> type, Annotation[] annotations) {
				return DependencyInjectedType.DIEqualsCool;
			}
			
			@Override
			public boolean appliesToType(Class<?> type, Annotation[] annotations) {
				return type.equals(DependencyInjectedType.class);
			}
		}),new PrimitiveTypeBinder()});
		Iterable<ActionParameterFillCandidate> fillers = new ArrayIterable<ActionParameterFillCandidate>(new ActionParameterFillCandidate[] {new DefaultValueFiller()});
		
		ActionParameterTransformer tr = new CompositeActionParameterTransformer(transformers,binders,fillers);
		return tr;
	}
	
	public void methodToTransform(Mammal m, double pi, int pid, @Default int userid) {
		
	}
	
	public void otherMethodToTransform(Mammal m, double pi, DependencyInjectedType diType, int pid, @Default int userid) {
			
	}
	
	class MockTransformResult implements ActionParameterTransformResult {

		private boolean run = false;
		private int optFieldValue;
		
		public MockTransformResult(int optFieldValue) {
			super();
			this.optFieldValue = optFieldValue;
		}

		@Override
		public void setOptionalFilledParameterCount(int count) {}

		@Override
		public void setParameters(Object[] o) {
			run = true;
			assertTrue(o.length==4);
			assertTrue(o[0]==Mammal.Tiger);
			assertTrue(o[1].equals(3.141592));
			assertTrue(o[2].equals(52));
			assertTrue(o[3].equals(optFieldValue));	
		}

		@Override
		public void setTotalOptionalParameterCount(int count) {}
		
		protected boolean gotCalled() {
			return run;
		}
		
	}
	
	public void testTransformer() throws SecurityException, NoSuchMethodException {
		ActionParameterTransformer tr = createTransformer();
		MockTransformResult result = new MockTransformResult(0);
		MockResponse resp = new MockResponse();
		
		tr.transform(getClass().getMethod("methodToTransform", new Class<?>[]{Mammal.class,Double.TYPE,Integer.TYPE,Integer.TYPE}), new String[]{"tiger","3.141592","52"}, result, new MockRequest("/"), resp);
		assertTrue(result.gotCalled());
		
		result = new MockTransformResult(4);
		tr.transform(getClass().getMethod("methodToTransform", new Class<?>[]{Mammal.class,Double.TYPE,Integer.TYPE,Integer.TYPE}), new String[]{"tiger","3.141592","52","4"}, result, new MockRequest("/"), resp);
		assertTrue(result.gotCalled());
		
		
		ActionParameterTransformer tr2 = createDITestTransformer();
		ActionParameterTransformResult result2 = new ActionParameterTransformResult() {
			
			@Override
			public void setTotalOptionalParameterCount(int count) {
				assertEquals(2, count);
			}
			
			@Override
			public void setParameters(Object[] o) {
				assertTrue(o.length==5);
				assertTrue(o[0]==Mammal.Tiger);
				assertTrue(o[1].equals(3.141592));
				assertTrue(o[2].equals(DependencyInjectedType.DIEqualsCool));
				assertTrue(o[3].equals(52));
				assertTrue(o[4].equals(0));
			}
			
			@Override
			public void setOptionalFilledParameterCount(int count) {
				assertEquals(0, count);
			}
		};
		Method m = getClass().getMethod("otherMethodToTransform", new Class<?>[]{Mammal.class,Double.TYPE,DependencyInjectedType.class, Integer.TYPE,Integer.TYPE});
		tr2.transform(m, new String[]{"tiger","3.141592","52"}, result2, new MockRequest("/"), resp);
	}
	
	
	public void testPackageRouter() throws SecurityException, NoSuchMethodException {
		PackageRouter pr = new PackageRouter(new ArrayIterable<String>(new String[]{this.getClass().getPackage().getName()+".testcontrollers"}));
		ControllerDescription cd;
		
		ActionParameterTransformer tr = createTransformer();
		MockResponse resp = new MockResponse();
		
		cd = pr.routeToController(new MockRequest("/list/list"),resp, tr);
		assertTrue(cd.getControllerClass().equals(dryven.unittest.testcontrollers.list.ListController.class));
		assertTrue(cd.getAction().getName().equals("index"));
		
		Request req = new MockRequest("/list/list/search/Bruno");
		
		cd = pr.routeToController(req,resp, tr);
		assertTrue(cd.getControllerClass().equals(dryven.unittest.testcontrollers.list.ListController.class));
		assertTrue(cd.getAction().getName().equals("search"));
		assertTrue(cd.getActionParameter(0).getClass().equals(String.class));
		assertTrue(cd.getActionParameter(0).equals("Bruno"));
		assertTrue(cd.getActionParameter(1)==req);
		
		cd = pr.routeToController(new MockRequest("/list/doit/5"),resp, tr);
		assertTrue(cd.getControllerClass().equals(dryven.unittest.testcontrollers.list.IndexController.class));
		assertTrue(cd.getAction().getName().equals("doIt"));
		assertTrue(cd.getActionParameter(0).getClass().equals(Integer.class));
		assertTrue(cd.getActionParameter(0).equals(new Integer(5)));
		cd = pr.routeToController(new MockRequest("/list/feed/0/WHALE"),resp, tr);
		assertTrue(cd.getControllerClass().equals(dryven.unittest.testcontrollers.list.IndexController.class));
		assertTrue(cd.getAction().getName().equals("feed"));
		assertTrue(cd.getActionParameter(0).getClass().equals(Boolean.class));
		assertTrue(cd.getActionParameter(0).equals(false));
		assertTrue(cd.getActionParameter(1).getClass().equals(dryven.unittest.testcontrollers.list.IndexController.Animal.class));
		assertTrue(cd.getActionParameter(1).equals(dryven.unittest.testcontrollers.list.IndexController.Animal.Whale));
		cd = pr.routeToController(new MockRequest("/"),resp, tr);
		assertTrue(cd.getControllerClass().equals(dryven.unittest.testcontrollers.IndexController.class));
		assertTrue(cd.getAction().getName().equals("index"));
		
		cd = pr.routeToController(new MockRequest(""),resp, tr);
		assertTrue(cd.getControllerClass().equals(dryven.unittest.testcontrollers.IndexController.class));
		assertTrue(cd.getAction().getName().equals("index"));
		assertTrue(cd.getParameters().length==2);
		assertTrue(cd.getActionParameter(0)==null);
		assertTrue(cd.getActionParameter(1)==null);
		
		
		cd = pr.routeToController(new MockRequest("/yohoo/"),resp, tr);
		assertTrue(cd.getControllerClass().equals(dryven.unittest.testcontrollers.IndexController.class));
		assertTrue(cd.getAction().getName().equals("index"));
		assertTrue(cd.getActionParameter(0).getClass().equals(String.class));
		assertTrue(cd.getActionParameter(0).equals("yohoo"));
		
		cd = pr.routeToController(new MockRequest("/yohoo/pig"),resp, tr);
		assertTrue(cd.getControllerClass().equals(dryven.unittest.testcontrollers.IndexController.class));
		assertTrue(cd.getAction().getName().equals("index"));
		assertTrue(cd.getActionParameter(0).getClass().equals(String.class));
		assertTrue(cd.getActionParameter(0).equals("yohoo"));
		assertTrue(cd.getActionParameter(1).getClass().equals(Animal.class));
		assertTrue(cd.getActionParameter(1).equals(Animal.Pig));
		
		cd = pr.routeToController(new MockRequest("/optparam/5"),resp, tr);
		assertTrue(cd.getControllerClass().equals(dryven.unittest.testcontrollers.optparam.IndexController.class));
		assertTrue(cd.getAction().equals(cd.getControllerClass().getMethod("index", Integer.TYPE)));
		assertTrue(cd.getActionParameter(0).equals(5));
		
		cd = pr.routeToController(new MockRequest("/optparam/"),resp, tr);
		assertTrue(cd.getControllerClass().equals(dryven.unittest.testcontrollers.optparam.IndexController.class));
		assertTrue(cd.getAction().equals(cd.getControllerClass().getMethod("index")));
		
		/*cd = pr.routeToController(new MockRequest("/", null);
		assertTrue(cd.getClass().equals(dryven.unittest.packagerouter.IndexController.class));
		assertTrue(cd.getAction().getName().equals("index"));*/
		
	}
	
	public void testDispatch() throws SecurityException, NoSuchMethodException {
		final Object[] values = {
			12,
			"hello",
			Flavour.Bitter,
			3.14f
		};
		final TestDependency tdep = new TestDependency(values);

		ControllerDescription cd = new ControllerDescription(TestController.class, TestController.class.getMethod("test", new Class<?>[]{Integer.TYPE,String.class,Flavour.class,Float.TYPE}), values);
		List<DependencyInjector> injectors = new LinkedList<DependencyInjector>();
		injectors.add(new DependencyInjector() {
			
			@Override
			public Object load(LocalThreadStorage storage, Class<?> type, Annotation[] annotations) {
				if(type.equals(TestDependency.class)) return tdep;
				//just an integer value we will inject into the constructor
				//this way we will test if the dep service calls the constructor with
				//only the biggest possible number of bound constructor arguments
				else return 5;	
			}
			
			@Override
			public boolean appliesToType(Class<?> type, Annotation[] annotations) {
				return type.equals(TestDependency.class) || type.equals(Integer.TYPE);
			}
		});
		DependencyService depService = new DefaultDependencyService(injectors);
		ForwardResult result = new ForwardResult(cd);
		result.injectDependencies(new DependencyInjectingControllerFactory(depService));
		ActionResult newResult = result.process(new MockRequest("/"), new MockResponse());
		assertTrue(tdep.isConstructorCalled());
		assertTrue(tdep.isMethodCalled());
	}
}
